/* 
 *  Copyright 2012 CodeMagi, Inc.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.codemagi.taglibs.taxonomy;

import com.codemagi.servlets.AppException;
import com.codemagi.servlets.BaseServlet;
import com.codemagi.servlets.TaxonomyController;
import com.codemagi.servlets.model.*;
import com.codemagi.util.Utils;
import java.io.IOException;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.log4j.Logger;

/**
 * This class is the base class for all taxonomy taglibs
 *
 * @version 1.0
 * @author August Detlefsen for CodeMagi, Inc.
 */
public class TaxonomyTag extends TagSupport {

    Logger log = Logger.getLogger(this.getClass());
    protected Integer categoryId;                   //the ID of the category
    protected String alias;                         //the category alias
    protected Node node;                            //the Node to set taxonomy for
    protected Integer nodeId;                       //Optionally a node ID instead of an actual Node
    protected String id;                            //javascript id of the input      
    protected String markupClass = "";            // CSS class for the input
    protected boolean disabled = false;         // if true, disables changes to this form field                 
    protected Map attributes = new HashMap(); // attributes of the <input> element
    protected boolean override = false;         // if true, overrides request and uses default
    protected boolean sort = true;
    protected boolean showTooltips = false;         // if true, some input types will show tooltips
    protected String tooltipImage = "";            // an image to display indicating a tooltip is present
    protected Comparator comparator;                // Comparator to use when sorting terms
    protected TaxonomyController controller;
    protected Category category;

    /**
     * Override setPageContext to load info about the taxonomy as soon as the
     * pageContext is available
     */
    public void setPageContext(PageContext pc) {
	super.setPageContext(pc);

	//get the appl context
	ServletContext application = pageContext.getServletContext();

	//get taxonomy controller
	controller = (TaxonomyController) application.getAttribute("taxonomy");
	log.debug("Controller: " + controller);
    }

    //SETTERS -------------------------------------------
    public void setCategoryId(Integer newValue) {
	categoryId = newValue;

	if (categoryId != null) {
	    category = controller.getCategory(categoryId);
	    log.debug("Category: " + category);
	    alias = category.getAlias();
	}
    }

    public void setCategoryId(String newValue) {
	try {
	    setCategoryId(TaxonomyController.convertInteger(newValue, true));
	} catch (AppException ae) {
	    log.debug("Invalid categoryId entered: " + categoryId, ae);
	}
    }

    public void setAlias(String newValue) {
	log.debug("setAlias( " + newValue + " )");
	alias = newValue;

	if (!Utils.isEmpty(alias)) {
	    category = controller.getCategory(alias);
	    log.debug("Category: " + category);
	    categoryId = category.getId();
	}

    }

    public void setNode(Node newValue) {
	node = newValue;
    }

    public void setNodeId(Integer newValue) {
	nodeId = newValue;

	if (nodeId != null && node == null) {
	    node = controller.getNode(nodeId);
	}
    }

    public void setId(String newValue) {
	id = newValue;
    }

    public void setMarkupClass(String newValue) {
	markupClass = newValue;
    }

    public void setDisabled(boolean x) {
	disabled = x;
    }

    public void setShowTooltips(boolean x) {
	showTooltips = x;
    }

    public void setTooltipImage(String newValue) {
	tooltipImage = newValue;
    }

    public void setComparator(Comparator newValue) {
	comparator = newValue;
    }

    public void setAttributes(Map x) {
	if (x != null) {
	    attributes = x;
	}
    }

    /**
     * Sets an attribute in the attributes map. Note that the contract for the
     * attributes Map is that it only contains Stings
     */
    public void appendAttribute(String key, String value) {
	if (Utils.isEmpty(key) || Utils.isEmpty(value)) {
	    return;
	}

	attributes = (HashMap) ((HashMap) attributes).clone();

	String actualKey = getKeyIgnoreCase(key, attributes);
	String actualValue = (String) attributes.get(actualKey);

	if (Utils.isEmpty(actualValue)) {
	    actualValue = value;
	} else {
	    actualValue = actualValue + value;
	}

	attributes.put(actualKey, actualValue);
    }

    public void setOverride(boolean x) {
	override = x;
    }

    public void setSort(boolean newValue) {
	sort = newValue;
    }

    //UTILITY METHODS ------------------------------------------------------------
    /**
     * Quote metacharacters in HTML.
     */
    public static String quote(String x) {
	if (x == null) {
	    return "";
	}

	// deal with ampersands first                                                                                  
	StringBuffer output = new StringBuffer(x);

	int index = 0;
	int targetLength = 1;
	int replaceLength = 5;

	while (true) {
	    index = output.toString().indexOf("&", index);

	    if (index == -1) {
		break;  //no match found                                                                           
	    }
	    //match found, check for &#                                                                                        
	    if ('#' != output.charAt(index + 1)) {
		output.replace(index, index + targetLength, "&amp;");
		index += replaceLength;

	    } else {
		index += targetLength;
	    }
	}

	x = output.toString();

	int c, oldC = -1;
	while ((c = x.indexOf('"')) != -1) {
	    x = new String((new StringBuffer(x)).replace(c, c + 1, "&quot;"));
	}
	while ((c = x.indexOf('<')) != -1) {
	    x = new String((new StringBuffer(x)).replace(c, c + 1, "&lt;"));
	}
	while ((c = x.indexOf('>')) != -1) {
	    x = new String((new StringBuffer(x)).replace(c, c + 1, "&gt;"));
	}
	return x;
    }

    /**
     * Print any HTML tag attributes that might have been passed to a
     * StringBuffer.
     *
     * @param out StringBuffer to append attributes to
     * @param attributes Map containing attribute values
     */
    public static void printAttributes(StringBuffer out, Map attributes)
	    throws JspTagException, IOException {

	if (attributes != null) {

	    // check for illegal keys                                                                                            
	    if (attributes.containsKey("name")
		    || attributes.containsKey("value")
		    || attributes.containsKey("type")
		    || attributes.containsKey("checked")) {
		throw new JspTagException("illegal key found in attributes Map");
	    }

	    //iterate the attributes and print them                                                             
	    Iterator i = attributes.keySet().iterator();
	    while (i.hasNext()) {
		Object oKey = i.next();
		Object oVal = attributes.get(oKey);

		/*                                                                                                              
		 * If the attribute contains non-Strings, give the user                                                          
		 * a more meaningful message than what he or she would get                                                         
		 * if we just propagated a ClassCastException back.                                                                
		 * (This'll get caught below.)                                                                                     
		 */
		if (!(oKey instanceof String)
			|| (oVal != null && !(oVal instanceof String))) {
		    throw new JspTagException("all members in attributes Map must be Strings");
		}

		String key = (String) oKey;
		String value = (String) oVal;

		// print the key (and value if appropriate)                                                               
		if (value == null) {
		    out.append("    " + quote(key) + "\n");
		} else {
		    out.append("    " + quote(key) + "=\"" + quote(value) + "\"\n");
		}
	    }
	}
    }

    /**
     * Print any HTML tag attributes that might have been passed to the JSP
     * output.
     *
     * @param out JspWriter to append attributes to
     * @param attributes Map containing attribute values
     */
    public static void printAttributes(JspWriter out, Map attributes)
	    throws JspTagException, IOException {

	StringBuffer output = new StringBuffer();

	printAttributes(output, attributes);

	out.print(output.toString());

    }

    /**
     * Returns the key for an attribute, regardless of case. For example, if the
     * input to this method is equal to 'style' and the key 'STYLE' is contained
     * within the attributes Map, 'STYLE' will be returned.
     */
    public static String getKeyIgnoreCase(String input, Map map) {
	if (Utils.isEmpty(input)) {
	    return "";
	}
	if (map == null) {
	    return input;
	}

	Iterator i = map.keySet().iterator();
	while (i.hasNext()) {
	    try {
		String key = (String) i.next();

		if (input.equalsIgnoreCase(key)) {
		    return key;
		}

	    } catch (ClassCastException cce) {
		continue;
	    }
	}

	return input;
    }

    /**
     * Returns a parameter from the request
     */
    protected String getParameter(String name, HttpServletRequest request) {

	if (ServletFileUpload.isMultipartContent(request)) {
	    //see if we have already parsed the params
	    Map requestParams = (Map) request.getAttribute("requestParams");

	    if (requestParams == null) {
		//parse param out of the multipart
		try {
		    requestParams = BaseServlet.parseCommonsMultipart(request);

		    //stick into request attributes so we don't have to re-parse for subsequent tags
		    request.setAttribute("requestParams", requestParams);

		} catch (Exception ae) {
		    log.debug("Error parsing multipart - This should never happen", ae);
		}

	    }

	    Object output = requestParams.get(name);
	    if (output instanceof String) {
		return (String) output;

	    } else if (output instanceof List) {
		return ((List<String>) output).get(0);

	    }

	} else {
	    return request.getParameter(name);

	}

	return null;
    }

    /**
     * Returns an array of parameter values from the request
     */
    protected String[] getParameterValues(String name, HttpServletRequest request) {

	if (ServletFileUpload.isMultipartContent(request)) {
	    //see if we have already parsed the params
	    Map requestParams = (Map) request.getAttribute("requestParams");

	    if (requestParams == null) {
		//parse param out of the multipart
		try {
		    requestParams = BaseServlet.parseCommonsMultipart(request);

		    //stick into request attributes so we don't have to re-parse for subsequent tags
		    request.setAttribute("requestParams", requestParams);

		} catch (Exception ae) {
		    log.debug("Error parsing multipart - This should never happen", ae);
		}

	    }

	    Object param = requestParams.get(name);
	    if (param instanceof String) {
		String[] output = {(String) param};
		return output;

	    } else if (param instanceof List) {
		String[] type = {""};
		return ((List<String>) param).toArray(type);

	    }

	} else {
	    return request.getParameterValues(name);

	}

	return null;
    }
}
